const fs = require('fs').promises;
const Koa = require('koa');
const path = require('path')
const chalk = require('chalk')
const static = require('koa-static')
const { parse } = require('es-module-lexer')
const MagicString = require('magic-string')
const { Readable } = require('stream')


async function readBody(stream) {


    if (stream instanceof Readable) {
        return new Promise((resolve) => {
            let res = ''
            stream.on('data', (chunk) => {
                res += chunk
            })
            stream.on('end', () => {
                resolve(res)
            })
        })
    } else {
        return stream
    }

}


const resolvePlugin = [
    ({ app, root }) => {
        //  重写模块的路径，加上 /@modules/vue 重写后浏览器会再次发送请求
        function rewrite(source) {
            const imports = parse(source)[0]
            const ms = new MagicString(source)
            imports.forEach((item) => {
                const { s, e } = item
                const target = source.substring(s, e)
                //  排除 ./ 或 /

                if (!/^[(\.\/) | \/]/.test(target)) {

                    ms.overwrite(s, e, `/@modules/${target}`)
                }

            })

            return ms.toString()
        }
        app.use(async (ctx, next) => {
            await next()

            if (ctx.body && ctx.response.is('.js') && !ctx.response.is('.import')) {
                const body = await readBody(ctx.body)

                const newBody = rewrite(body)
                ctx.body = newBody
            }
        })
    },
    //  拦截/@modules/vue
    ({ app, root }) => {
        app.use(async (ctx, next) => {
            const reg = /\/@modules\//g
            //     若没有匹配则跳过

            if (!reg.test(ctx.path)) {
                return next()
            }

            const target = ctx.path.replace(reg, '');
            const fileMap = {
                vue: path.resolve(root, 'node_modules', '@vue/runtime-dom/dist/runtime-dom.esm-browser.js'),
            }


            const body = await fs.readFile(fileMap[target], 'utf8');

            ctx.type = 'js';
            ctx.body = body

        })
    },
    //  解析 css
    ({ app, root }) => {
        const supportConstructorCssStyle = () => {
            try {
                new CSSStyleSheet()
                return true
            } catch (e) {
                console.log('the browser don`t support CSSStyleSheet');
            }
            return false
        }
        app.use(async (ctx, next) => {
            const cssExp = /\.css/g
            if (!cssExp.test(ctx.path)) {
                return next()
            }

            const cssPath = path.join(root, ctx.path)

            const css = await fs.readFile(cssPath)

            let cssCode = '';
            cssCode =
                `const style = new CSSStyleSheet();
let css = ${JSON.stringify(css.toString())};
style.replaceSync(css); 
document.adoptedStyleSheets = [...document.adoptedStyleSheets, style];
export default css
`
            ctx.set({
                'Content-Type': 'application/javascript;'
            })
            ctx.body = cssCode
        })
    },
    //  解析 .vue 文件

    ({ app, root }) => {
        app.use(async (ctx, next) => {
            const vueExp = /\.vue$/g
            if (!vueExp.test(ctx.path)) {
                return next()
            }
            const filePath = path.join(root, ctx.path)
            const fileContent = await fs.readFile(filePath, 'utf8')
            const { compileTemplate, parse } = require(path.resolve(root, 'node_modules', '@vue/compiler-sfc/dist/compiler-sfc.cjs'))
            const { descriptor } = parse(fileContent)
            if (!ctx.query.type) {
                const template = descriptor.template && descriptor.template.content
                const script = descriptor.script && descriptor.script.content
                let code = ''
                if (script) {
                    code += script.replace(/(.*)export default/, `$1const __script=`)

                }
                if (template) {
                    const templatePath = ctx.path + '?type=template'
                    code += `\nimport { render as __render } from "${templatePath}"`;
                    code += `\n__script.render = __render`
                }
                code += '\nexport default __script'
                ctx.type = 'js'
                ctx.body = code
            } else if (ctx.query && ctx.query.type === 'template') {
                ctx.type = 'js';
                const template = descriptor.template && descriptor.template.content
                const { code } = compileTemplate({ source: template })
                ctx.body = code
            }

        })
    },
    //  文件
    ({ app, root })=>{
        app.use(async (ctx, next) => {
            const imgExp = /\.png/g
            if (!imgExp.test(ctx.path)) {
                return next()
            }
    
            const imgPath = path.join(root, ctx.path)
    
            const img = await fs.readFile(imgPath)
    
            ctx.type = 'jpg'
            ctx.body = img
            ctx.status = 200

        })
        
    },
    ({ app, root }) => {

        app.use(static(root))



    }

]

function createServer() {
    const app = new Koa();

    const context = {
        app,
        root: process.cwd()
    }

    resolvePlugin.forEach(plugin => plugin(context))
    return app
}

createServer().listen(4000, () => {
    console.log(' Dev server running at:')
    console.log(` > Local: ${chalk.cyan('http://localhost:4000/')}`)
})